﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Nate_sRoguelikeGameXNA.GameObjects.Architecture;
using Nate_sRoguelikeGameXNA.Levels;

namespace Nate_sRoguelikeGameXNA
{
    enum RoomSize
    {
        Small = 0,
        Medium,
        Large
    }

    class Room
    {
        # region Instance
        # region Fields

        public Rectangle OuterPerimeter { get; private set; }
        public Level Level { get; private set; }
        // Returns the inside of the room
        public Rectangle InnerPerimeter
        {
            get
            {
                var innerPerimeter = OuterPerimeter;
                innerPerimeter.Inflate(-1, -1);

                return innerPerimeter;
            }
        }
        public List<Door> Doors { get; private set; }
        public List<Room> _rooms;
        private Rectangle _levelDimensions;
        private GameArchitecture _wallType;
        private string _name;

        # endregion

        # region Constructor
        public Room(Level level, string name, GameArchitecture wallType, List<Room> rooms, Rectangle levelDimensions)
        {
            _name = name;
            Level = level;
            OuterPerimeter = new Rectangle(0, 0, 0, 0);
            _rooms = rooms;
            _levelDimensions = levelDimensions;
            _wallType = wallType;
            Doors = new List<Door>();
        }
        public Room(Level level, GameArchitecture wallType, Rectangle sourceRectangle)
        {
            Level = level;
            OuterPerimeter = new Rectangle(0, 0, 0, 0);
            _rooms = level.Rooms;
            _levelDimensions = sourceRectangle;
            _wallType = wallType;
            Doors = new List<Door>();
            int rectangleArea = sourceRectangle.GetArea();
            int numberOfDice = Math.Max(1, level.Dimensions.GetArea() / rectangleArea);
            // Should the room fill the entire rectangle?
            // The probability goes down as the room size goes up
            bool fillRectangle = numberOfDice > 100 || Game.Roll(numberOfDice, Math.Max(1, (int)Math.Ceiling(100f / numberOfDice))) >= 100;
            if (fillRectangle)
                OuterPerimeter = sourceRectangle;
            else
            {
                // Should we calculate X or Y first?
                int sizeX, sizeY;

                if (Game.Roll(1, 2) == 1)
                {
                    sizeX = Game.Roll(3, sourceRectangle.Width / 2, 5, sourceRectangle.Width - 1, 4);
                    sizeY = Game.Roll(3, sourceRectangle.Height / 2, Math.Max(5, rectangleArea / 8 / sizeX));
                }
                else
                {
                    sizeY = Game.Roll(3, sourceRectangle.Height / 2, 5, sourceRectangle.Height - 1, 4);
                    sizeX = Game.Roll(3, sourceRectangle.Width / 2, Math.Max(5, rectangleArea / 8 / sizeY));
                }
                sizeX = Math.Min(Math.Min(sourceRectangle.Width, sizeX), 15);
                sizeY = Math.Min(Math.Min(sourceRectangle.Height, sizeY), 10);
                OuterPerimeter = new Rectangle(sourceRectangle.X + Game.Roll(1, sourceRectangle.Width - sizeX, true),
                                               sourceRectangle.Y + Game.Roll(1, sourceRectangle.Height - sizeY, true),
                                               sizeX,
                                               sizeY);
            }
        }
        # endregion

        # region Methods
        public void AddToMap(MapBlock[][] map)
        {
            int l = map.Length;

            for (int x = OuterPerimeter.Left; x <= OuterPerimeter.Right; x++)
                for (int y = OuterPerimeter.Top; y <= OuterPerimeter.Bottom; y++)
                    if (y == OuterPerimeter.Top || y == OuterPerimeter.Bottom || x == OuterPerimeter.Left || x == OuterPerimeter.Right)
                        map[x][y].Architecture = _wallType;
                    else
                        map[x][y].Architecture = Level.BaseArchitecture;

            foreach (var door in Doors)
                door.AddToMap();
        }

        public Door CreateDoor()
        {
            Door door = null;

            while (door == null || Level.GetMapBlockAt(door.Outside) == null)
            {
                int x, y;
                // Randomly choose X or Y first
                // Make sure the door is not on a corner of or inside the room
                if (Game.Roll(1, 2) == 1)
                {
                    x = OuterPerimeter.Left + Game.Roll(1, OuterPerimeter.Width, true);
                    if (x == OuterPerimeter.Left || x == OuterPerimeter.Right)
                        y = OuterPerimeter.Top + Game.Roll(1, OuterPerimeter.Height - 1);
                    else
                        y = Game.Roll(1, 2) == 1 ? OuterPerimeter.Top : OuterPerimeter.Bottom;
                }
                else
                {
                    y = OuterPerimeter.Top + Game.Roll(1, OuterPerimeter.Height, true);
                    if (y == OuterPerimeter.Top || y == OuterPerimeter.Bottom)
                        x = OuterPerimeter.Left + Game.Roll(1, OuterPerimeter.Width - 1);
                    else
                        x = Game.Roll(1, 2) == 1 ? OuterPerimeter.Left : OuterPerimeter.Right;
                }
                var block = Level.GetMapBlockAt(x, y);
                if (block != null)
                    door = new Door(this, block);
            }
            Doors.Add(door);

            return door;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (Game.Debug)
            {

                foreach (var door in Doors)
                {
                    door.Draw(spriteBatch);
                }
            }
        }

        public void JoinWith(Room room)
        {
            var r1p = new Point[4];
            r1p[0] = new Point(OuterPerimeter.Center.X, OuterPerimeter.Top);
            r1p[1] = new Point(OuterPerimeter.Right, OuterPerimeter.Center.Y);
            r1p[2] = new Point(OuterPerimeter.Center.X, OuterPerimeter.Bottom);
            r1p[3] = new Point(OuterPerimeter.Left, OuterPerimeter.Center.Y);

            var r2p = new Point[4];
            r2p[0] = new Point(room.OuterPerimeter.Center.X, room.OuterPerimeter.Top);
            r2p[1] = new Point(room.OuterPerimeter.Right, room.OuterPerimeter.Center.Y);
            r2p[2] = new Point(room.OuterPerimeter.Center.X, room.OuterPerimeter.Bottom);
            r2p[3] = new Point(room.OuterPerimeter.Left, room.OuterPerimeter.Center.Y);
            Dictionary<double, Point[]> sideDistances = new Dictionary<double, Point[]>();
            foreach (var side1 in r1p)
                foreach (var side2 in r2p)
                {
                    var distance = side1.Distance(side2);
                    if (!sideDistances.ContainsKey(distance))
                        sideDistances.Add(distance, new[] { side1, side2 });
                }
            
            var closestWalls = sideDistances.OrderBy(v => v.Key).First().Value;
            var door1 = PlaceDoor(this, closestWalls[0]);
            var door2 = PlaceDoor(room, closestWalls[1]);
            Doors.Add(door1);
            room.Doors.Add(door2);
        }

        public bool Place()
        {
            int attempts = 0;
            do
            {
                OuterPerimeter =
                    new Rectangle(
                        Math.Min(Game.Roll(2, (_levelDimensions.Width - OuterPerimeter.Width / 2) / 2),
                                 _levelDimensions.Width - 5),
                        Math.Min(Game.Roll(2, (_levelDimensions.Height - OuterPerimeter.Height / 2) / 2),
                                 _levelDimensions.Height - 5), 0, 0);
                attempts++;
                if (attempts == 10)
                {
                    // Screw random placement...
                    // Discard the room
                    return false;
                }
            } while (_rooms.Any(r => r.OuterPerimeter.Intersects(OuterPerimeter)));
            return true;
        }

        public bool Resize()
        {
            int attempts = 0;
            RoomSize roomSizeCategory = RoomSize.Small;
            do
            {
                if (attempts > 0)
                    Level.RoomCountsBySize[(int)roomSizeCategory]--;
                int roomSize = Game.Roll(1, 30);

                int minSize = 4;
                int maxArea, maxX, maxY;
                int minArea = 16;
                int sizeX = 0, sizeY = 0;

                // Small room
                if (roomSize < 6)
                {
                    maxArea = 32;
                    sizeX = Game.Roll(1, maxArea / minSize, minSize);
                    sizeY = Game.Roll(1, maxArea / sizeX, minSize);
                    roomSizeCategory = RoomSize.Small;
                }
                // Medium room
                else if (roomSize < 27)
                {
                    maxArea = 64;
                    minArea = 46;
                    sizeX = Game.Roll(1, maxArea / minSize, 6);
                    sizeY = Game.Roll(1, maxArea / sizeX, Math.Max(minSize, minArea / sizeX));
                    roomSizeCategory = RoomSize.Medium;
                }
                // Big room
                else
                {
                    maxArea = 256;
                    minArea = 48;
                    sizeX = Game.Roll(1, maxArea / 10, 10);
                    sizeY = Game.Roll(2, maxArea / sizeX / 2, Math.Max(minSize, minArea / sizeX));
                    roomSizeCategory = RoomSize.Large;
                }
                // Update room counts
                Level.RoomCountsBySize[(int)roomSizeCategory]++;

                // Randomly swap X & Y
                if (Game.Roll(1, 2) == 1)
                {
                    int temp = sizeX;
                    sizeX = sizeY;
                    sizeY = sizeX;
                }
                // Create BoundingBox constrained to fit the level
                OuterPerimeter = new Rectangle(OuterPerimeter.X,
                                            OuterPerimeter.Y,
                                            Math.Min(sizeX, _levelDimensions.Width - OuterPerimeter.X - 1),
                                            Math.Min(sizeY, _levelDimensions.Height - OuterPerimeter.Y - 1));
                attempts++;

                if (attempts == 10)
                {
                    // Screw random placement...
                    // Discard the room 
                    return false;
                }
            } while (_rooms.Any(r => r.OuterPerimeter.Intersects(OuterPerimeter)));
            bool intersects = _rooms.Any(r => r.OuterPerimeter.Intersects(OuterPerimeter));
            Rectangle borderRectangle = OuterPerimeter;
            borderRectangle.Inflate(1, 1);
            //borderRectangle.Offset(-1, -1);
            var intersectedRoom = _rooms.Where(r => r.OuterPerimeter.Intersects(borderRectangle)).FirstOrDefault();
            if (intersectedRoom != null)
            {
                Rectangle intersectedRect = borderRectangle;
                Rectangle newRect = OuterPerimeter;

                intersectedRect.Intersect(intersectedRoom.OuterPerimeter);
                // Move the smallest amount to get away
                if (intersectedRect.Width > intersectedRect.Height)
                    // Up or down?
                    newRect.Offset(0, borderRectangle.Y > intersectedRoom.OuterPerimeter.Y ? intersectedRect.Height : -intersectedRect.Height);
                else
                    // Left or right?
                    newRect.Offset(borderRectangle.X > intersectedRoom.OuterPerimeter.X ? intersectedRect.Width : -intersectedRect.Width, 0);
                if (_levelDimensions.Contains(newRect) && !_rooms.Any(r => r.OuterPerimeter.Intersects(newRect)))
                    OuterPerimeter = newRect;
            }
            if (_levelDimensions.Right == OuterPerimeter.Right || _levelDimensions.Bottom == OuterPerimeter.Bottom)
                return Place();

            return true;
        }

        # endregion
        # endregion
        # region Static

        public static Door PlaceDoor(Room room, Point closestSide)
        {
            MapBlock mapBlock = null;
            bool chooseX = closestSide.X == room.OuterPerimeter.Center.X;
            while (mapBlock == null)
            {
                if (chooseX)
                    mapBlock = room.Level.GetMapBlockAt(room.OuterPerimeter.X + Game.Roll(1, room.OuterPerimeter.Width - 1),
                                                   closestSide.Y);
                else
                    mapBlock = room.Level.GetMapBlockAt(closestSide.X,
                                                   room.OuterPerimeter.Y + Game.Roll(1, room.OuterPerimeter.Height - 1));
            }
            return new Door(room, mapBlock);
        }

        # endregion
    }
}
